package no.gnet.edvd.web.controller;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.StringTokenizer;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;

import no.gnet.edvd.DataStore;
import no.gnet.edvd.engines.Engine;
import no.gnet.edvd.types.ActivityType;
import no.gnet.edvd.types.Player;
import no.gnet.edvd.types.Transfer;
import no.gnet.edvd.web.types.EdvdSessionUser;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.paypal.svcs.services.AdaptivePaymentsService;
import com.paypal.svcs.types.ap.PayRequest;
import com.paypal.svcs.types.ap.PayResponse;
import com.paypal.svcs.types.ap.PaymentDetailsRequest;
import com.paypal.svcs.types.ap.PaymentDetailsResponse;
import com.paypal.svcs.types.ap.Receiver;
import com.paypal.svcs.types.ap.ReceiverList;
import com.paypal.svcs.types.common.RequestEnvelope;

@Controller
public class PaymentController extends CommonController {

	private static final String PAYPAL_PAYMENT_COMPLETED = "COMPLETED";

	private static final String PENDING_TRANS = "PENDING_TRANS";

	private static Logger logger = Logger.getLogger(PaymentController.class);

	@Autowired
	DataStore datastore;

	@Autowired
	Engine engine;

	@Autowired
	ServletContext servletContext;

	@RequestMapping(value = "/pay", method = RequestMethod.POST, produces = "application/json", headers = { "Content-type=application/json" })
	public @ResponseBody
	String addBet(@RequestBody List<Transfer> transfers, HttpServletRequest request) throws ServletException {
		logger.info("Received transfer to pay: " + transfers);
		checkLoggedOn(request);		
		String transferkey = toid(transfers);
		String returnpage = request.getRequestURL().toString().replace("/pay","/paymentlanding");
		String ipnpage = request.getRequestURL().toString().replace("/pay","/ipn");
		logger.info("Before merge: ");
		printTranses(transfers);
		List<Transfer> realTransfers = mergeTransfersToSameReceiver(transfers);
		logger.info("After merge: ");
		printTranses(realTransfers);		
		PayResponse payResponse = simplePay(realTransfers, returnpage,transferkey,ipnpage);
		String payKey = payResponse.getPayKey();
		storePendingPayment(transfers,payKey,request, transferkey);
		return payKey;
	}

	private void printTranses(List<Transfer> transfers) {
		for(Transfer t:transfers){
			logger.info(t);			
		}
	}

	public List<Transfer> mergeTransfersToSameReceiver(List<Transfer> transfers) {
		Hashtable <String,Transfer> transferMap = new Hashtable <String,Transfer>();
		for(Transfer transfer:transfers){
			String emailTO = transfer.getTo().email;
			Transfer existingTransfer = transferMap.get(emailTO);
			if(existingTransfer!=null){
				existingTransfer.setAmount(existingTransfer.getAmount()+transfer.getAmount());
			} else {
				transferMap.put(emailTO,transfer);
			}
		}
		return new ArrayList<Transfer>(transferMap.values());
	}

	private void storePendingPayment(List<Transfer> transfers, String payKey,HttpServletRequest request, String toid) {
		request.getSession().setAttribute(PENDING_TRANS+toid, payKey);
	}
	
	private String toid(List<Transfer> transfers) {
		String id="";
		for(Transfer t:transfers){
			id+=t.id+"_";
		}
		if(id.endsWith("_")){
			id=id.substring(0, id.length()-1);
		}
		return id;
	}

	private String getPendingPayment(String transferkey, HttpServletRequest request) {
		return (String) request.getSession().getAttribute(PENDING_TRANS+transferkey);
	}

	
	@RequestMapping(value = "/paymentlanding")
	public String paymentLanding(@RequestParam("transferid") String transferid,  
			HttpServletRequest request){
		Player authenticatedUser = checkLoggedOn(request);		
		String paykey=getPendingPayment(transferid, request);
		if(paykey==null){
			throw new IllegalStateException("No pending payment for transfer " + transferid);
		}
		PaymentDetailsResponse presp = paymentDetails(paykey);
		// * COMPLETED - The payment was successful
		if(presp.getStatus().equals(PAYPAL_PAYMENT_COMPLETED)){
			logger.warn("At landingpage, payment completed, transfer key is " + transferid+", paykey is " + paykey);
			int [] paidTransfers=extractTransfers(transferid);
			for (int i = 0; i < paidTransfers.length; i++) {
				datastore.setPaidTransfer(paidTransfers[i]);
				datastore.logActivity(ActivityType.PAYMENT, "Payment done, transfer.id is " + paidTransfers[i] , authenticatedUser);
			}
			markSessionDirty(request);
		} else {
			logger.warn("At landingpage, but payment not completed, transfer is " + transferid+", paykey is " + paykey);
		}
		return "redirect:../#usersettings";
	}

	
	private int[] extractTransfers(String transferid) {
		StringTokenizer st = new StringTokenizer(transferid,"_");
		List<Integer> list = new ArrayList<Integer>();
		while(st.hasMoreTokens()){
			list.add(new Integer(Integer.parseInt(st.nextToken())));
		}
		return toIntArray(list);
	}

	int[] toIntArray(List<Integer> list){
		  int[] ret = new int[list.size()];
		  for(int i = 0;i < ret.length;i++)
		    ret[i] = list.get(i);
		  return ret;
		}	
	
	@RequestMapping(value = "/ipn")
	public String ipn(@RequestParam("transferid") int transferid,  
			HttpServletRequest request){
		logger.warn("AT IPN, TRANSFERID IS " + transferid);
		return "redirect:../#usersettings";
	}	
	
	
	private Player checkLoggedOn(HttpServletRequest request) {
		EdvdSessionUser sessionUser = sessionUser(request);
		if(sessionUser==null){
			throw new IllegalStateException("Requires authenticated session");
		}
		Player player = sessionUser.getPlayer();
		if(player==null || !sessionUser.authenticated){
			throw new IllegalStateException("Requires authenticated session");
		}
		return player;
	}

	public PayResponse simplePay(List<Transfer> transfers, String requesturl, String transferkey, String ipnpage) {

		// ##PayRequest
		// The code for the language in which errors are returned, which must be
		// en_US.
		RequestEnvelope requestEnvelope = new RequestEnvelope();
		requestEnvelope.setErrorLanguage("en_US");
		List<Receiver> receiverLst = new ArrayList<Receiver>();
		String senderEmail=null;
		for (Transfer transfer : transfers) {
			Receiver receiver1 = new Receiver(transfer.getAmount());
			receiver1.setEmail(transfer.getTo().email);
			receiverLst.add(receiver1);
			senderEmail=transfer.getFrom().email;
		}

		PayRequest payRequest = new PayRequest(requestEnvelope, "PAY",
				requesturl+"?transferid=" + transferkey, "USD", new ReceiverList(receiverLst),
				requesturl+"?transferid=" + transferkey);
		payRequest.setSenderEmail(senderEmail);

		// The URL to which you want all IPN messages for this payment to be
		// sent.
		// This URL supersedes the IPN notification URL in your profile
		payRequest.setIpnNotificationUrl(ipnpage+"/ipn"+"?transferid=" + transferkey);

		PayResponse payResponse = makeAPICall(payRequest);
		return payResponse;

	}

	private PayResponse makeAPICall(PayRequest payRequest) {

		Logger logger = Logger.getLogger(this.getClass().toString());

		// ## Creating service wrapper object
		// Creating service wrapper object to make API call and loading
		// configuration file for your credentials and endpoint
		AdaptivePaymentsService service = null;
		try {
			service = new AdaptivePaymentsService(getClass().getResourceAsStream("/sdk_config.properties"));
		} catch (IOException e) {
			logger.error("Error Message : " + e.getMessage(), e);
		}
		PayResponse payResponse = null;
		try {

			// ## Making API call
			// Invoke the appropriate method corresponding to API in service
			// wrapper object
			payResponse = service.pay(payRequest);
		} catch (Exception e) {
			logger.error("Error Message : " + e.getMessage(), e);
		}

		// ## Accessing response parameters
		// You can access the response parameters using getter methods in
		// response object as shown below
		// ### Success values
		if (payResponse.getResponseEnvelope().getAck().getValue().equalsIgnoreCase("Success")) {

			// The pay key, which is a token you use in other Adaptive
			// Payment APIs (such as the Refund Method) to identify this
			// payment. The pay key is valid for 3 hours; the payment must
			// be approved while the pay key is valid.
			logger.info("Pay Key : " + payResponse.getPayKey());

			// Once you get success response, user has to redirect to PayPal
			// for the payment. Construct redirectURL as follows,
			// `redirectURL=https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_ap-payment&paykey="
			// + payResponse.getPayKey();`
		}
		// ### Error Values
		// Access error values from error list using getter methods
		else {
			logger.error("API Error Message : " + payResponse.getError().get(0).getMessage());
		}
		return payResponse;

	}

	public PaymentDetailsResponse paymentDetails(String paykey) {

		Logger logger = Logger.getLogger(this.getClass().toString());

		// ##PaymentDetailsRequest
		// The code for the language in which errors are returned, which must be
		// en_US.
		RequestEnvelope requestEnvelope = new RequestEnvelope();
		requestEnvelope.setErrorLanguage("en_US");

		// PaymentDetailsRequest which takes,
		//
		// `Request Envelope` - Information common to each API operation, such
		// as the language in which an error message is returned.
		PaymentDetailsRequest paymentDetailsRequest = new PaymentDetailsRequest(requestEnvelope);

		// You must specify either,
		//
		// * `Pay Key` - The pay key that identifies the payment for which you
		// want to retrieve details. This is the pay key returned in the
		// PayResponse message.
		// * `Transaction ID` - The PayPal transaction ID associated with the
		// payment. The IPN message associated with the payment contains the
		// transaction ID.
		// `paymentDetailsRequest.setTransactionId(transactionId)`
		// * `Tracking ID` - The tracking ID that was specified for this payment
		// in the PayRequest message.
		// `paymentDetailsRequest.setTrackingId(trackingId)`
		paymentDetailsRequest.setPayKey(paykey);

		// ## Creating service wrapper object
		// Creating service wrapper object to make API call and loading
		// configuration file for your credentials and endpoint
		AdaptivePaymentsService service = null;
		try {
			service = new AdaptivePaymentsService(getClass().getResourceAsStream("/sdk_config.properties"));
		} catch (IOException e) {
			logger.error("Error Message : " + e.getMessage(), e);
		}
		PaymentDetailsResponse paymentDetailsResponse = null;
		try {

			// ## Making API call
			// Invoke the appropriate method corresponding to API in service
			// wrapper object
			paymentDetailsResponse = service.paymentDetails(paymentDetailsRequest);
		} catch (Exception e) {
			logger.error("Error Message : " + e.getMessage(), e);
		}

		// ## Accessing response parameters
		// You can access the response parameters using getter methods in
		// response object as shown below
		// ### Success values
		if (paymentDetailsResponse.getResponseEnvelope().getAck().getValue().equalsIgnoreCase("Success")) {

			// The status of the payment. Possible values are:
			//
			// * CREATED - The payment request was received; funds will be
			// transferred once the payment is approved
			// * COMPLETED - The payment was successful
			// * INCOMPLETE - Some transfers succeeded and some failed for a
			// parallel payment or, for a delayed chained payment, secondary
			// receivers have not been paid
			// * ERROR - The payment failed and all attempted transfers
			// failed
			// or all completed transfers were successfully reversed
			// * REVERSALERROR - One or more transfers failed when
			// attempting
			// to reverse a payment
			// * PROCESSING - The payment is in progress
			// * PENDING - The payment is awaiting processing
			logger.info("Payment Status : " + paymentDetailsResponse.getStatus());
		}
		// ### Error Values
		// Access error values from error list using getter methods
		else {
			logger.error("API Error Message : " + paymentDetailsResponse.getError().get(0).getMessage());
		}
		return paymentDetailsResponse;
	}

}
